{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Class Body Scope"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The class body is a scope and therefore has it's own namespace. Inside that scope we can reference symbols like we would within any other scope:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Language:\n",
    "    MAJOR = 3\n",
    "    MINOR = 7\n",
    "    REVISION = 4\n",
    "    FULL = '{}.{}.{}'.format(MAJOR, MINOR, REVISION)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'3.7.4'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Language.FULL"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However, functions defined inside the class are not nested in the body scope - instead they are nested in whatever scope the class itself is in."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This means that we cannot reference the class symbols inside a function without also telling Python where to look for it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Language:\n",
    "    MAJOR = 3\n",
    "    MINOR = 7\n",
    "    REVISION = 4\n",
    "    \n",
    "    @property\n",
    "    def version(self):\n",
    "        return '{}.{}.{}'.format(self.MAJOR, self.MINOR, self.REVISION)\n",
    "    \n",
    "    @classmethod\n",
    "    def cls_version(cls):\n",
    "        return '{}.{}.{}'.format(cls.MAJOR, cls.MINOR, cls.REVISION)\n",
    "    \n",
    "    @staticmethod\n",
    "    def static_version():\n",
    "        return '{}.{}.{}'.format(Language.MAJOR, Language.MINOR, Language.REVISION)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'3.7.4'"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "l = Language()\n",
    "l.version"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'3.7.4'"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Language.cls_version()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'3.7.4'"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Language.static_version()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Basically think that the function symbols are in the class body namespace, but the functions themselves are defined externally to the class - just as if we had written it this way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def full_version():\n",
    " return '{}.{}.{}'.format(Language.MAJOR, Language.MINOR, Language.REVISION)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'3.7.4'"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "full_version()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So writing something like this will not work:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Language:\n",
    "    MAJOR = 3\n",
    "    MINOR = 7\n",
    "    REVISION = 4\n",
    "    \n",
    "    @classmethod\n",
    "    def cls_version(cls):\n",
    "        return '{}.{}.{}'.format(MAJOR, MINOR, REVISION)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'MAJOR' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-10-7a3f9fd88d68>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mLanguage\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcls_version\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m<ipython-input-9-18992503fb74>\u001b[0m in \u001b[0;36mcls_version\u001b[0;34m(cls)\u001b[0m\n\u001b[1;32m      6\u001b[0m     \u001b[0;34m@\u001b[0m\u001b[0mclassmethod\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      7\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mcls_version\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0;34m'{}.{}.{}'\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mformat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mMAJOR\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mMINOR\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mREVISION\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m: name 'MAJOR' is not defined"
     ]
    }
   ],
   "source": [
    "Language.cls_version()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This behavior can lead to subtle bugs if we aren't careful. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What happens if the names `MAJOR`, `MINOR` and `REVISION` **are** defined in the enclosing scope?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "MAJOR = 0\n",
    "MINOR = 0\n",
    "REVISION = 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'0.0.1'"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Language.cls_version()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "See what happened?!!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now of course, the nested scopes follow the same usual rules, so we could technically have something like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "MAJOR = 0\n",
    "MINOR = 0\n",
    "REVISION = 1\n",
    "\n",
    "def gen_class():\n",
    "    MAJOR = 0\n",
    "    MINOR = 4\n",
    "    REVISION = 2\n",
    "    \n",
    "    class Language:\n",
    "        MAJOR = 3\n",
    "        MINOR = 7\n",
    "        REVISION = 4\n",
    "\n",
    "        @classmethod\n",
    "        def version(cls):\n",
    "            return '{}.{}.{}'.format(MAJOR, MINOR, REVISION)\n",
    "        \n",
    "    return Language"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "cls = gen_class()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'0.4.2'"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cls.version()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice how the scope of `version` was nested inside `gen_class` which itself is nested in the `global` scope.\n",
    "\n",
    "When we called the `version` method, it found the `MAJOR`, `MINOR` and `REVISION` in the closest enclosing scope - which turned out to be the `gen_class` scope.\n",
    "\n",
    "This means by the way, that `version` is not only a method, but actually a closure."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "import inspect"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ClosureVars(nonlocals={'MAJOR': 0, 'MINOR': 4, 'REVISION': 2}, globals={}, builtins={'format': <built-in function format>}, unbound=set())"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "inspect.getclosurevars(cls.version)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This last example of \"unexpected\" behavior I want to show you was show to me by a friend who was puzzled by it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "name = 'Guido'\n",
    "\n",
    "class MyClass:\n",
    "    name = 'Raymond'\n",
    "    list_1 = [name] * 3\n",
    "    list_2 = [name.upper() for i in range(3)]\n",
    "    \n",
    "    @classmethod\n",
    "    def hello(cls):\n",
    "        return '{} says hello'.format(name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['Raymond', 'Raymond', 'Raymond']"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "MyClass.list_1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since the expression `[name] * 3` lives in the class body, it uses `name` that it finds in the class namespace."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Guido says hello'"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "MyClass.hello()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here, `name` is used inside a function, so the closest `name` symbol is the one in the module/global scope. Hence we see that `Guido` was used."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['GUIDO', 'GUIDO', 'GUIDO']"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "MyClass.list_2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "That one is more puzzling... Why is the expression `[name.upper() for i in range(3)]` using `name` from the enclosing (module/global) scope, and not the one from the class namespace like `[name] * 3` did?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Remember what we discussed about comprehensions?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "They are essentially thinly veiled **functions**!!!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So they behave like a function would, and therefore are not nested in the class body scope, but, in this case, in the module/global scope!"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
